package com.peking.donations.plugins.pay.wx.utils;

import com.google.common.collect.Maps;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.security.*;
import java.util.Map;

/**
 * HTTPS 请求
 * 
 * @author Created by YGR on 16/3/3.
 */
public class HTTPSRequestFactory {

    private static HTTPSRequestFactory factory;

    public static HTTPSRequestFactory getInstance() {
        factory = new HTTPSRequestFactory();
        return factory;
    }

    public HTTPSRequestFactory() {
        loadRequestConfig();
    }

    private Logger logger = LoggerFactory.getLogger(getClass());

    private static Map<Configure, SSLConnectionSocketFactory> httpClientMap = Maps.newConcurrentMap();

    // 连接超时时间，默认10秒
    private int socketTimeout = 10000;

    // 传输超时时间，默认30秒
    private int connectTimeout = 30000;

    // 请求器的配置
    private RequestConfig requestConfig;

    public String sendPost(String api, String xmlObj, Configure configure) throws WxpayException, IOException {
        SSLConnectionSocketFactory sslConnectionSocketFactory = httpClientMap.get(configure);
        if (null == sslConnectionSocketFactory) {
            sslConnectionSocketFactory = addSSLConn(configure);
            httpClientMap.put(configure, sslConnectionSocketFactory);
        }
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();

        HttpPost httpPost = new HttpPost(api);
        StringEntity postData = new StringEntity(xmlObj, "UTF-8");
        httpPost.setEntity(postData);
        CloseableHttpResponse response = httpClient.execute(httpPost);

        try {
            HttpEntity entity = response.getEntity();

            if (entity != null) {
                StringBuilder builder = new StringBuilder();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"),
                        8 * 1024);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    builder.append(line);
                }

                EntityUtils.consume(entity);
                return bufferedReader.toString();
            }
        } catch (Exception e) {
            logger.error("https 请求发生异常:", e);
            throw new WxpayException("https 请求发生异常", e);
        } finally {
            response.close();
            httpClient.close();
        }

        return null;
    }

    /**
     * 根据配置初始化一个HTTPS请求
     * 
     * @param configure
     * @return
     * @throws KeyStoreException
     * @throws IOException
     * @throws WxpayException
     * @throws UnrecoverableKeyException
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private SSLConnectionSocketFactory addSSLConn(Configure configure) throws WxpayException, IOException {
        KeyStore keyStore = null;
        FileInputStream inputStream = null;
        try {
            keyStore = KeyStore.getInstance("PKCS12");
            inputStream = new FileInputStream(new File(configure.getCertLocalPath()));// 加载本地的证书进行https加密传输
            keyStore.load(inputStream, configure.getCertPassword().toCharArray());// 设置证书密码
        } catch (Exception e) {
            logger.error("https 请求证书加载错误:", e);
            throw new WxpayException("https 请求证书加载错误", e);
        } finally {
            inputStream.close();
        }

        try {
            // Trust own CA and all self-signed certs
            SSLContext sslcontext = SSLContexts.custom()
                    .loadKeyMaterial(keyStore, configure.getCertPassword().toCharArray()).build();
            // Allow TLSv1 protocol only
            SSLConnectionSocketFactory sslCon = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" },
                    null, NoopHostnameVerifier.INSTANCE);

            return sslCon;
        } catch (Exception e) {
            logger.error("https client 生成错误:", e);
            throw new WxpayException("https client 生成错误", e);
        }
    }

    /**
     * 根据默认超时限制初始化requestConfig
     */
    private void loadRequestConfig() {
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout)
                .build();
    }

}
